/*
	memory_bitmap.c

	contains functionality to produce a page allocation bitmap based on a BIOS generated memory map

	Author: Aidan Goddard 24/4/13
*/

#include "../kernel/headers/types.h"
#include "headers/printf.h"

#define PAGE_SIZE 0x200000

//typedef struct MMAP_entry MMAP_entry;
struct __attribute__((packed)) MMAP_entry
{
	uint64_t base;
	uint64_t length;
	uint32_t type;
	uint32_t flags;
};

// the page allocation structure
//typedef struct PAGE_ALLOCATION PAGE_ALLOCATION;
struct __attribute__((packed)) PAGE_ALLOCATION
{
	// page allocation bitmap
	uint8_t bitmap[131072];
	uint32_t page_size;
	uint64_t highest_allocatable_page;
	uint32_t last_allocated_page;

	// access control
	// other required stuff goes here, defined later

};

// global pointer to page allocation bitmap
struct PAGE_ALLOCATION *allocation = (struct PAGE_ALLOCATION*)(0x600000 + KOFFSET);

// functions to set the page state and get the page state
// returns the state of the page (0 = free, 1 = used)
static uint64_t GetPageState(uint64_t address)
{
	// get page number from address
	uint32_t page = address / PAGE_SIZE;

	// get base address of page allocation bitmap
	uint32_t *ptr = (uint32_t*)allocation;

	// get the entry dword and bit in dword positions
	uint32_t entry = page / 32;
	uint32_t bit = page % 32;

	// get the dword and shift it and mask accordingly
	uint64_t to_return = ptr[entry];
	to_return = to_return >> bit;
	to_return = to_return & 1;

	// return it
	return to_return;
}

// sets the page state at the given address (0 = free, non-zero = used). returns base address of page
static uint64_t SetPageState(uint64_t address, uint64_t state)
{
	// get page number from address
	uint32_t page = address / PAGE_SIZE;

	// get base address of page allocation bitmap
	uint32_t *ptr = (uint32_t*)allocation;

	// get the entry dword and bit in dword positions
	uint32_t entry = page / 32;
	uint32_t bit = page % 32;

	// set the bit value
	if(state == 0)
	{
		uint32_t value = 1 << bit;
		value = ~value;
		ptr[entry] = ptr[entry] & value;
	}
	else
	{
		uint32_t value = 1 << bit;
		ptr[entry] = ptr[entry] | value;
	}

	return page;
}

// 64 bit function to mangle the page entries
static void Mangle64(struct MMAP_entry *entries, uint16_t entry_count)
{
	// don't need to do a lot here. just print them
	Printf("\n[MEMORY ] Base               | Length             | Type");
	int i;
	for(i = 0; i < entry_count; i++)
	{
		Printf("\n[MEMORY ] 0x%a | 0x%a | %u", entries[i].base, entries[i].length, entries[i].type);
	}

	// change lengths to end of sections
	for(i = 0; i < entry_count; i++)
	{
		// get new section end
		uint64_t end = entries[i].base - 1;
		end = end + entries[i].length;
		entries[i].length = end;
	}

	// print new memory map
	Printf("\n[MEMORY ] updated memory map:");
	Printf("\n[MEMORY ] Base               | Limit              | Type");
	for(i = 0; i < entry_count; i++)
	{
		Printf("\n[MEMORY ] 0x%a | 0x%a | %u", entries[i].base, entries[i].length, entries[i].type);
	}



	return;
}

// 64 bit function to create the bitmap
uint64_t CreateBitmap64(struct MMAP_entry *entries, int entry_count)
{
	// something to store the available page count in
	uint64_t page_count = 0;

	// set the page size
	allocation->page_size = PAGE_SIZE;

	// first mark everything as used
	uint64_t i;
	for(i = 0; i < 131072; i++)
	{
		allocation->bitmap[i] = ~0;
	}

	// find highest page base address in table
	uint64_t highest_address = 0;
	for(i = 0; i < entry_count; i++)
	{
		if(entries[i].length > highest_address)
		{
			highest_address = entries[i].length;
		}
	}
	Printf("\n[MEMORY ] highest addressable byte 0x%a", highest_address);

	// set pages to free according to memory table (starting at 512MB - after kernel reserved space)
	// for each free entry in the table
	for(i = 0; i < entry_count; i++)
	{
		// check the entry type for "free"
		if(entries[i].type != 1)
		{
			continue;
		}

		// entry is free
		// get address start and address end
		uint64_t address_start = entries[i].base;
		uint64_t address_end = entries[i].length;
		uint64_t range_start = ((address_start / PAGE_SIZE) * PAGE_SIZE);
		if(range_start != 0)
			range_start -= PAGE_SIZE;
		uint64_t range_end = ((address_end / PAGE_SIZE) * PAGE_SIZE) + PAGE_SIZE;

		// now set the pages covered by the address range to unused
		uint64_t  k;
		for(k = range_start; k <= range_end; k += PAGE_SIZE)
		{
			// check if current page falls within entry range
			uint64_t current_page = k;
			if(!(((current_page + PAGE_SIZE) >= address_start) && (current_page <= address_end)))
			{
				// not in the range
				continue;
			}

			// in the range, mark the current page as free
			SetPageState(current_page, 0);

		}
	}

	// set pages to used according to memory table (starting at 512MB - after kernel reserved space)
	// for each used entry in the table
	for(i = 0; i < entry_count; i++)
	{
		// check the entry type for "non-free"
		if(entries[i].type == 1)
		{
			continue;
		}

		// entry is free
		// get address start and address end
		uint64_t address_start = entries[i].base;
		uint64_t address_end = entries[i].length;
		uint64_t range_start = ((address_start / PAGE_SIZE) * PAGE_SIZE);
		if(range_start != 0)
			range_start -= PAGE_SIZE;
		uint64_t range_end = ((address_end / PAGE_SIZE) * PAGE_SIZE) + PAGE_SIZE;

		// now set the pages covered by the address range to unused
		uint64_t k;
		for(k = range_start; k <= range_end; k += PAGE_SIZE)
		{
			// check if current page falls within entry range
			uint64_t current_page = k;
			if(!(((current_page + PAGE_SIZE) >= address_start) && (current_page <= address_end)))
			{
				// not in the range
				continue;
			}

			// in the range, mark the current page as free
			SetPageState(current_page, 1);

		}
	}

	// set first 512MB to used
	for(i = 0; i < 32; i++)
	{
		allocation->bitmap[i] = ~0;
	}

	// count how many free pages there are
	uint64_t highest_page = 0;
	for(i = 0; i <= (highest_address + 1); i += PAGE_SIZE)
	{
		if(GetPageState(i) == 1)
		{
			continue;
		}

		page_count++;
		highest_page = i;
	}
	allocation->highest_allocatable_page = highest_page;

	Printf("\n[MEMORY ] highest page 0x%a free pages %u", highest_page, page_count);

	// return the amount of available memory
	return page_count * PAGE_SIZE;
}


// function to build a page bitmap. returns amount of available memory in bytes
uint64_t GetMemoryBitmap()
{
	// get pointer to entries and the entry count
	struct MMAP_entry *entries = (struct MMAP_entry*)(0xe000 + KOFFSET);
	uint16_t *entry_count = (uint16_t*)(0xfff0 + KOFFSET);

	// print
	Printf("\n[MEMORY ] %u entries", *entry_count);

	// mangle the entries
	Mangle64(entries, *entry_count);

	// get the bitmap
	Printf("\n[MEMORY ] page allocation bitmap at 0x%a", allocation);

	return CreateBitmap64(entries, *entry_count);
}

























